﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApp
{
    public class TileCoordinateCalculator
    {
        /// <summary>
        /// 瓦片大小（像素），与gdal2tiles参数保持一致
        /// </summary>
        public const int TileSize = 512;

        /// <summary>
        /// 地球半径（米），WGS84椭球
        /// </summary>
        private const double EarthRadius = 6378137.0;

        /// <summary>
        /// 最大纬度（Web Mercator投影限制）
        /// </summary>
        public const double MaxLatitude = 85.05112877980659;

        /// <summary>
        /// 最小纬度（Web Mercator投影限制）
        /// </summary>
        public const double MinLatitude = -MaxLatitude;
        public static void test()
        {
    
            int zoomLevel = 20;
            int tileX = 869473;
            int tileY = 613656;
            // gdal2tiles 默认生成是TMS格式瓦片图, 生成XYZ格式需要添加参数 --xyz
            // XYZ → 原点左上，Leaflet 默认支持，Google/OSM 都用它
            // TMS → 原点左下，老标准，Leaflet 需要 tms:true 才能正确显示
            bool isTms = true; // 如果瓦片是TMS格式，设为true；XYZ格式设为false
            (double lon, double lat)[] corners = CalculateTileCorners(zoomLevel, tileX, tileY, isTms);
            (double lon, double lat) center = CalculateTileCenter(zoomLevel, tileX, tileY, TileSize, isTms);
            Console.WriteLine("四角坐标:");
            Console.WriteLine($"左上: {FormatLonLat(corners[0])}");
            Console.WriteLine($"右上: {FormatLonLat(corners[1])}");
            Console.WriteLine($"右下: {FormatLonLat(corners[2])}");
            Console.WriteLine($"左下: {FormatLonLat(corners[3])}");
            Console.WriteLine($"中心: {FormatLonLat(center)}");

            // (118.509521484375, 29.31274631057796)
            (double lon, double lat) topleft = CalculatePixelLonLat(zoomLevel, tileX, tileY, 0, 0, TileSize, isTms);
            Console.WriteLine($"左上1: {FormatLonLat(topleft)}");
            (double lon, double lat) topright = CalculatePixelLonLat(zoomLevel, tileX, tileY, 511, 0, TileSize, isTms);
            Console.WriteLine($"右上1: {FormatLonLat(topright)}");
            (double lon, double lat) bottomright = CalculatePixelLonLat(zoomLevel, tileX, tileY, 511, 511, TileSize, isTms);
            Console.WriteLine($"右下1: {FormatLonLat(bottomright)}");
            (double lon, double lat) bottomleft = CalculatePixelLonLat(zoomLevel, tileX, tileY, 0, 511, TileSize, isTms);
            Console.WriteLine($"左下1: {FormatLonLat(bottomleft)}");
            (double lon, double lat) center1 = CalculatePixelLonLat(zoomLevel, tileX, tileY, 256, 256, TileSize, isTms);
            Console.WriteLine($"中心1: {FormatLonLat(center1)}");
            Console.WriteLine();
        }
  

        /// <summary>
        /// 根据瓦片编号和缩放级别计算瓦片四个角的经纬度
        /// </summary>
        /// <param name="zoom">缩放级别</param>
        /// <param name="x">瓦片X坐标</param>
        /// <param name="y">瓦片Y坐标</param>
        /// <param name="isTms"></param>
        /// <returns>包含四个角经纬度的数组，顺序为：左上、右上、右下、左下</returns>
        public static (double lon, double lat)[] CalculateTileCorners(int zoom, long x, long y, bool isTms = true)
        {
            if (isTms) y = FlipY(y, zoom);
            return new[]
            {
                // 左上角（实际地理上的西北 corner）
                TileXYToLonLat(x, y, zoom),
                // 右上角（实际地理上的东北 corner）
                TileXYToLonLat(x + 1, y, zoom),
                // 右下角（实际地理上的东南 corner）
                TileXYToLonLat(x + 1, y + 1, zoom),
                // 左下角（实际地理上的西南 corner）
                TileXYToLonLat(x, y + 1, zoom)
            };
        }

        /// <summary>
        /// 计算瓦片中心点的经纬度
        /// </summary>
        /// <param name="zoom">缩放级别</param>
        /// <param name="x">瓦片X坐标</param>
        /// <param name="y">瓦片Y坐标</param>
        /// <param name="tileSize"></param>
        /// <param name="isTms"></param>
        /// <returns>中心点的经纬度</returns>
        public static (double lon, double lat) CalculateTileCenter(int zoom, long x, long y, int tileSize = 512, bool isTms = true)
        {
            // 计算中心点对应的像素坐标（瓦片中心）
            return CalculatePixelLonLat(zoom, x, y, tileSize / 2, tileSize / 2, tileSize, isTms);
        }

        /// <summary>
        /// 计算瓦片上任意像素点的经纬度
        /// </summary>
        /// <param name="zoom">缩放级别</param>
        /// <param name="tileX">瓦片X坐标</param>
        /// <param name="tileY">瓦片Y坐标</param>
        /// <param name="pixelX">像素X坐标（0到TileSize-1）</param>
        /// <param name="pixelY">像素Y坐标（0到TileSize-1）</param>
        /// <param name="tileSize"></param>
        /// <param name="isTms"></param>
        /// <returns>像素点的经纬度</returns>
        public static (double lon, double lat) CalculatePixelLonLat(int zoom, long tileX, long tileY, int pixelX, int pixelY, int tileSize = 512, bool isTms = true)
        {
            if (isTms) tileY = FlipY(tileY, zoom);

            if (pixelX < 0 || pixelX >= tileSize || pixelY < 0 || pixelY >= tileSize)
            {
                throw new ArgumentOutOfRangeException("像素坐标必须在0到瓦片大小之间");
            }
            
            // 计算像素在瓦片网格中的精确位置
            // 关键修正：像素Y坐标增加表示向南移动（与地理方向一致）
            double x = tileX + (double)pixelX / tileSize;
            double y = tileY + (double)pixelY / tileSize;

            double totalTiles = Math.Pow(2, zoom);

            // 计算经度
            double lon = (x / totalTiles) * 360.0 - 180.0;

            // 计算纬度
            double latRad = Math.PI * (1 - 2 * y / totalTiles);
            //latRad = Math.PI * (2 * y / totalTiles - 1);
            double lat = Math.Atan(Math.Sinh(latRad)) * 180.0 / Math.PI;

            return (lon, lat);
        }

        /// <summary>
        /// 将瓦片坐标转换为经纬度
        /// </summary>
        /// <param name="x">瓦片X坐标（可以是小数）</param>
        /// <param name="y">瓦片Y坐标（可以是小数）</param>
        /// <param name="zoom">缩放级别</param>
        /// <returns>经纬度坐标</returns>
        private static (double lon, double lat) TileXYToLonLat(double x, double y, int zoom)
        {
            //double n = Math.PI - (2.0 * Math.PI * y) / Math.Pow(2.0, zoom);

            //double longitude = (x / Math.Pow(2.0, zoom) * 360.0) - 180.0;
            //double latitude = 180.0 / Math.PI * Math.Atan(Math.Sinh(n));

            //return (longitude, latitude);

            // 计算缩放级别对应的瓦片总数
            double totalTiles = Math.Pow(2, zoom);

            // 计算经度：X坐标转换
            // 公式来源：https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames
            double lon = (x / totalTiles) * 360.0 - 180.0;

            // 计算纬度：Y坐标转换（关键修正）
            // 公式来源：https://epsg.io/3857
            double n = Math.PI - (2.0 * Math.PI * y) / totalTiles;
            double lat = 180.0 / Math.PI * Math.Atan(Math.Sinh(n));
            return (lon, lat);
        }

        /// <summary>
        /// 辅助方法：将经纬度转换为瓦片坐标（用于验证）
        /// </summary>
        public static (double x, double y) LonLatToTileXY(double longitude, double latitude, int zoom)
        {
            // 确保纬度在有效范围内
            if (latitude < MinLatitude)
                latitude = MinLatitude;
            else if (latitude > MaxLatitude)
                latitude = MaxLatitude;

            // 标准化经纬度
            double latRad = latitude * Math.PI / 180.0;
            double lonRad = longitude * Math.PI / 180.0;

            // 计算总瓦片数
            double totalTiles = Math.Pow(2, zoom);

            // 计算X坐标
            double x = (longitude + 180.0) / 360.0 * totalTiles;

            // 计算Y坐标（使用正投影公式）
            double y = (1.0 - Math.Log(Math.Tan(latRad) + 1.0 / Math.Cos(latRad)) / Math.PI) / 2.0 * totalTiles;

            return (x, y);
        }
        private static int FlipY(long y, int z)
        {
            long n = 1L << z; // 2^z
            return (int)(n - 1 - y);
        }

        private static string FormatLonLat((double lon, double lat) coord)
        {
            string lonStr = coord.lon >= 0 ? $"{coord.lon:F8}°E" : $"{-coord.lon:F8}°W";
            string latStr = coord.lat >= 0 ? $"{coord.lat:F8}°N" : $"{-coord.lat:F8}°S";
            return $"{lonStr}, {latStr}";
        }

        /// <summary>
        /// 从瓦片文件名解析出缩放级别和瓦片坐标
        /// 支持类似 "20/869472/613656.png" 这样的文件名格式
        /// </summary>
        /// <param name="tileFileName">瓦片文件名或路径</param>
        /// <param name="zoom">解析出的缩放级别</param>
        /// <param name="x">解析出的瓦片X坐标</param>
        /// <param name="y">解析出的瓦片Y坐标</param>
        /// <returns>解析成功返回true，否则返回false</returns>
        public static bool ParseTileFileName(string tileFileName, out int zoom, out long x, out long y)
        {
            zoom = 0;
            x = 0;
            y = 0;

            try
            {
                // 清理路径，只保留文件名部分
                string fileName = System.IO.Path.GetFileNameWithoutExtension(tileFileName);
                string fullPath = System.IO.Path.GetFullPath(tileFileName);

                // 分割路径获取各级目录
                string[] pathParts = fullPath.Split(new[] { System.IO.Path.DirectorySeparatorChar, System.IO.Path.AltDirectorySeparatorChar },
                                                   StringSplitOptions.RemoveEmptyEntries);

                // 查找缩放级别、x、y所在的位置
                if (pathParts.Length >= 3)
                {
                    // 假设格式为 .../zoom/x/y.png
                    zoom = int.Parse(pathParts[pathParts.Length - 3]);
                    x = long.Parse(pathParts[pathParts.Length - 2]);
                    y = long.Parse(pathParts[pathParts.Length - 1]);
                    return true;
                }
                return false;
            }
            catch
            {
                return false;
            }
        }
    }
}
